﻿#region Using

using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XNgine.Misc;
using XNgine.ScreenManagement.StandardScreens;
using XNgine.Services;

#endregion

namespace XNgine.ScreenManagement
{
    /// <summary>
    /// This is a Manager class for GameScreens, it decides when to update and
    /// draw the screens, it also takes care of transitions and content loading/unloading
    /// </summary>
    public sealed class ScreenManager : DrawableGameComponent, IScreenManagement
    {
        #region Screens Collection

        private readonly NamedItemCollection<IGameScreen> _screens = new NamedItemCollection<IGameScreen>();
        /// <summary>
        /// The collection of the screens that this class is managing
        /// YOU SHOULD NEVER ADD OR REMOVE ELEMENTS BY HAND (USE MANAGER METHODS INSTEAD)
        /// </summary>
        public NamedItemCollection<IGameScreen> Screens
        {
            get { return _screens; }
        }

        #endregion

        #region Helper variables

        private readonly List<IGameScreen> eligableScreens = new List<IGameScreen>();

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the active screen
        /// </summary>
        public IGameScreen ActiveScreen
        {
            get { return Screens.GetLast(); }
            set
            {
                Screens.Remove(value);
                Screens.Add(value);
            }
        }

        #endregion
        
        #region Loading and Initialization

        public ScreenManager()
            : base(XEngine.Instance)
        {
        }

        public override void Initialize()
        {
            base.Initialize();
            foreach (IGameScreen screen in Screens)
                screen.Initialize();
        }

        protected override void LoadContent()
        {
            base.LoadContent();
            foreach (IGameScreen scr in Screens)
                scr.LoadContent();
        }

        protected override void UnloadContent()
        {
            base.UnloadContent();
            foreach (IGameScreen scr in Screens)
                scr.UnloadContent();
        }

        #endregion

        #region Main loop

        /// <summary>
        /// Update the screens that need to be updated
        /// </summary>
        public override void Update(GameTime gameTime)
        {
            EvaluateForInputProcessing();

            RepopulateEligableScreensList();

            // BlocksUpdate and OverrideUpdateBlocked check
            // also, load screen content if it is not allready loaded
            for (int i = Screens.Count - 1; i >= 0; i--)
            {
                if (!Screens[i].ContentLoaded) Screens[i].LoadContent();
                if (Screens[i].BlocksUpdate)
                {
                    if (i > 0)
                    {
                        for (int j = i - 1; j >= 0; j--)
                        {
                            if (!Screens[j].OverrideUpdateBlocked)
                                eligableScreens.Remove(Screens[j]);
                        }
                    }
                    break;
                }
            }

            // Update remaining components
            foreach (IGameScreen screen in eligableScreens)
            {
                if (screen.Initialized)
                {
                    screen.Update();
                }
            }
        }

        /// <summary>
        /// Draw the screens that need to be drawn
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            RepopulateEligableScreensList();

            // Clear the back buffer
            XEngine.Instance.GraphicsDevice.Clear(Color.CornflowerBlue);

            // BlocksDraw and OverrideDrawBlocked logic
            for (int i = Screens.Count - 1; i >= 0; i--)
            {
                if (Screens[i].BlocksDraw)
                {
                    if (i > 0)
                    {
                        for (int j = i - 1; j >= 0; j--)
                        {
                            if (!Screens[j].OverrideDrawBlocked)
                                eligableScreens.Remove(Screens[j]);
                        }
                    }
                    break;
                }
            }

            // Draw the remaining screens
            foreach (IGameScreen screen in eligableScreens)
                if (screen.Initialized)
                    screen.Draw();
        }

        /// <summary>
        /// Checks the screens to see if they need to be allowed input processing and allowes them to
        /// </summary>
        private void EvaluateForInputProcessing()
        {
            eligableScreens.Clear();

            // Populate the temp list
            foreach (IGameScreen screen in Screens)
            {
                eligableScreens.Add(screen);
                screen.IsInputAllowed = false;
            }
            // BlocksInput and OverrideInputBlocked login
            for (int i = Screens.Count - 1; i >= 0; i--)
            {
                if (Screens[i].BlocksInput)
                {
                    if (i > 0)
                        for (int j = i - 1; j >= 0; j--)
                            if (!Screens[j].OverrideInputBlocked)
                                eligableScreens.Remove(Screens[j]);

                    break;
                }
            }
            // Set IsInputAllowed for all GameScreens
            foreach (IGameScreen screen in Screens)
                if (!screen.InputDisabled)
                    screen.IsInputAllowed = eligableScreens.Contains(screen);
                else
                    screen.IsInputAllowed = false;
        }

        private void RepopulateEligableScreensList()
        {
            eligableScreens.Clear();

            // Populate the temp list
            foreach (IGameScreen screen in Screens)
                eligableScreens.Add(screen);
        }

        #endregion

        #region Core Functionality Methods

        private void AddScreen(IGameScreen screen)
        {
            Screens.Add(screen);
        }

        public void Show(string name)
        {
            ActiveScreen = Screens[name];
        }

        public void Show(IGameScreen screen)
        {
            if (!Screens.Contains(screen))
                AddScreen(screen);
            ActiveScreen = screen;
        }

        public void CloseScreen(IGameScreen screen)
        {
            screen.IsInputAllowed = false;
            Screens.Remove(screen);
        }

        public void CloseAllScreens(bool unload)
        {
            if (unload)
                UnloadContent();
            Screens.Clear();
        }

        public MessageBox ShowMessageBox(string msg)
        {
            MessageBox messageBox = new MessageBox(msg);
            Show(messageBox);
            return messageBox;
        }

        #endregion
    }
}